import * as PIXI from "pixi.js";
import { toolType, toolEvent } from "./tools/index";
import { exportType } from "./export/index";
import * as plugs from "./plugs/index";

export default class PxCanvas {
  constructor(options = {}) {
    this.resolution = 20;
    this.width = 24;
    this.height = 24;
    this.color = ["0x000000", "0xFFFFFF"];
    this.bgColors = ["0xD9D9d9", "0xFFFFFF"];
    this.toolSelect = toolType.PEN;
    this.el = document.body;
    this.updateSuccess = () => {};
    this.plugs = [];
    this.historyMax = 15;
    Object.assign(this, options);
    this.pointColor = this.color[0];
    this.pxMap = [];
    this.historyList = [];
    this.redoList = [];
    this.app = null;
    this.container = null;
    this.backgroundContainer = null;
    this.previewContainer = null;
    this.shapeContainer = null;
    this.isUp = true;
    this.first = true;
    this.size = 1;
    this.noMenu();
    this.extend = {};
    this.x = -1;
    this.y = -1;
    this.eventName = "pointerUp";
    for (const key of this.plugs) {
      this.extend[key] = plugs.default[key].bind(this);
    }
    return this;
  }
  init() {
    let { resolution, width, height, el } = this;
    this.app = new PIXI.Application({
      width: width,
      height: height,
      backgroundColor: 0xffffff,
      resolution: resolution || 1,
      antialias: true,
      preserveDrawingBuffer: true,
    });
    this.backgroundContainer = new PIXI.Container();
    this.container = new PIXI.Container();
    this.previewContainer = new PIXI.Container();
    this.shapeContainer = new PIXI.Container();
    this.app.stage.addChild(this.backgroundContainer);
    this.app.stage.addChild(this.container);
    this.app.stage.addChild(this.shapeContainer);
    this.app.stage.addChild(this.previewContainer);
    el.appendChild(this.app.view);
    this.createData();
    this.draw();
    this.event = {};
    return this;
  }
  destroy() {
    this.app.destroy(true, true);
  }
  update() {
    this.drawGraphics();
    this.updateSuccess(this);
  }
  on(eventName, callback) {
    this.event[eventName] = (...args) => {
      callback && callback(...args);
    };
  }
  emit(eventName, args) {
    this.event[eventName](args);
  }
  createData() {
    let { width, height, bgColors } = this;
    for (let i = 0; i < width; ++i) {
      for (let j = 0; j < height; ++j) {
        let bgColor = bgColors[(i + j) % 2];
        let x = i;
        let y = j;
        let alpha = 0;
        this.addPxMap({ x, y, color: "", alpha, bgColor });
      }
    }
  }
  async createBase64() {
    return await this.app.renderer.plugins.extract.base64(this.container);
  }
  async createPixels() {
    return await this.app.renderer.plugins.extract.pixels(this.container);
  }
  noMenu() {
    ["contextmenu"].forEach(function (ev) {
      document.addEventListener(ev, function (event) {
        return (event.returnValue = false);
      });
    });
  }
  clear() {
    this.addHistoryColors();
    this.pxMap.forEach((item) => {
      item.color = "";
      item.alpha = 0;
    });
    this.update();
  }
  cancel() {
    let list = this.historyList;
    if (list.length == 0) return;
    let redoColors = [];
    this.pxMap.forEach((item) => {
      redoColors.push(item);
    });
    this.redoList.push(redoColors);
    let colors = list.pop();
    this.pxMap = colors;
    this.update();
  }
  redo() {
    let list = this.redoList;
    if (list.length == 0) return;
    let historyColors = [];
    this.pxMap.forEach((item) => {
      historyColors.push(item);
    });
    this.historyList.push(historyColors);
    let colors = list.pop();
    this.pxMap = colors;
    this.update();
  }
  render(map = []) {
    map.forEach((item, index) => {
      this.pxMap[index].color = item.color;
      this.pxMap[index].alpha = item.alpha || 1;
    });
    this.update();
  }
  draw() {
    this.drawBackground();
    this.drawPreviewContainer();
    this.graphics = new PIXI.Graphics();
    this.container.addChild(this.graphics);
    this.update();
  }
  drawBackground() {
    const { pxMap } = this;
    let _graphics = new PIXI.Graphics();
    pxMap.forEach((px) => {
      const { x, y, bgColor } = px;
      _graphics.beginFill(bgColor);
      _graphics.drawRect(x, y, 1, 1);
      _graphics.endFill();
    });
    this.backgroundContainer.addChild(_graphics);
  }
  drawPreviewContainer(pxs) {
    const { pxMap } = this;
    if (this.previewContainer.children.length == 0) {
      let _graphics = new PIXI.Graphics();
      _graphics.cacheAsBitmap = false;
      _graphics.interactive = true;
      _graphics.buttonMode = true;
      _graphics.on("pointerdown", this.__pointerDown.bind(this));
      _graphics.on("pointerup", this.__pointerUp.bind(this));
      _graphics.on("mousemove", this.__pointerMove.bind(this));
      pxMap.forEach((px) => {
        const { x, y, color } = px;
        let alpha = color == "" ? 0 : 0.5;
        _graphics.beginFill(0xffffff, alpha);
        _graphics.drawRect(x, y, 1, 1);
        _graphics.endFill();
      });
      this.previewContainer.addChild(_graphics);
      return;
    }
    let _graphics = this.previewContainer.children[0];
    _graphics.clear();
    if (pxs.length == 0) return;
    let _pxs = pxs;
    let simple = [toolType.PEN, toolType.ERASER];
    if (!simple.includes(this.toolSelect)) {
      _pxs.length = 1;
    }
    pxMap.forEach((px) => {
      const { x, y } = px;
      let alpha = 0;
      if (_pxs.find((p) => p.x == x && p.y == y)) {
        alpha = 0.25;
      }
      _graphics.beginFill(0x000000, alpha);
      _graphics.drawRect(x, y, 1, 1);
      _graphics.endFill();
    });
  }
  export(type) {
    if (!exportType[type]) return;
    return exportType[type]({
      main: this,
      position: "center", // left,
    });
  }
  setToolType(type) {
    if (!toolType[type]) return;
    this.toolSelect = type;
  }
  setColor(color, type = 0) {
    this.color[type] = color;
  }
  addHistoryColors() {
    let colors = [];
    if (this.historyList.length > this.historyMax) this.historyList.shift();
    colors =JSON.parse(JSON.stringify(this.pxMap));
    this.historyList.push(colors);
    this.redoList.length = 0;
  }
  drawGraphics() {
    this.graphics.clear();
    for (let i = 0, len = this.pxMap.length; i < len; ++i) {
      const { x, y, color, alpha } = this.pxMap[i];
      this.graphics.beginFill(color || 0xffffff, alpha);
      this.graphics.drawRect(x, y, 1, 1);
      this.graphics.endFill();
    }
  }
  addPxMap(arg) {
    const { x, y, color, bgColor, alpha } = arg;
    this.pxMap.push({
      x,
      y,
      color,
      bgColor,
      alpha,
    });
  }
  toHex(color) {
    return PIXI.utils.string2hex(color.toString());
  }
  __pointerDown(event) {
    let point = event.data.global;
    this.eventName = "pointerDown";
    let pxs = this.findPx(point);
    if (pxs.length == 0) return;
    let which = event.data.originalEvent.which;
    this.pointColor = this.color[which == 1 ? 0 : 1];
    this.isUp = false;
    if (this.toolSelect != toolType.TUBES) this.addHistoryColors();
    toolEvent[this.toolSelect](pxs, this, which);
    this.drawGraphics();
  }
  __pointerMove(event) {
    let point = event.data.global;
    this.eventName = "pointerMove";
    this.x = ~~point.x;
    this.y = ~~point.y;
    this.emit("pointerMove", this);
    if (
      point.x < 0 ||
      point.x > this.width ||
      point.y < 0 ||
      point.y > this.height
    ){
      this.__pointerOut();
    }
    let pxs = this.findPx(point);
    this.drawPreviewContainer(pxs);
    if (pxs.length == 0) return;
    toolEvent[this.toolSelect](pxs, this);
  }
  __pointerUp(event) {
    this.eventName = "pointerUp";
    if (!this.isUp) this.update();
    this.isUp = true;
    toolEvent[this.toolSelect]([], this);
  }
  __pointerOut(event) {
    this.eventName = "pointerOut";
    if (!this.isUp) this.update();
    this.isUp = true;
    toolEvent[this.toolSelect]([], this);
  }
  setSize(size) {
    this.size = size;
  }
  findPx(point) {
    let index = this.pxMap.findIndex(
      (px) => px.x == ~~point.x && px.y == ~~point.y
    );
    let px = this.pxMap[index];
    if (!px) return [];
    const { size, height } = this;
    if (size == 1) return [px];
    let n = ~~(size / 2);
    let pxs = [];
    for (let i = -n; i < n + (size % 2); i++) {
      for (let j = -n; j < n + (size % 2); j++) {
        let v = size % 2 ? 0 : 1;
        let _index = index + height * (i + v) + j + v;
        let p = this.pxMap[_index];
        if (p && _index != index && p.y >= (px.y - n) && p.y <= (px.y + n)) {
          pxs.push(p);
        }
      }
    }
    return [px, ...pxs];
  }
}
